﻿// =============================================================================
// TNTTools SetterMethod.cs
// Copyright (c) 2013 TNTTechnologies Inc.
// =============================================================================

#region

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

#endregion

namespace TNTTools.Reflection{
    public class SetterMethod{
        // Spaced like this so you can hold left alt and drag blocks.
        // Note: these are null if incorrect setter.
        // Note: these are public for performance, they should be private.
        public Func<object, Boolean, object> boolSetter;
        public Func<object, Byte, object> byteSetter;
        public Func<object, char, object> charSetter;
        public Func<object, DateTime, object> dateTimeSetter;
        public Func<object, decimal, object> decimalSetter;
        public Func<object, double, object> doubleSetter;
        public Func<object, Int16, object> int16Setter;
        public Func<object, Int32, object> int32Setter;
        public Func<object, Int64, object> int64Setter;
        public Func<object, object, object> objectSetter;
        public Func<object, sbyte, object> sbyteSetter;
        public Func<object, Single, object> singleSetter;
        public Func<object, string, object> stringSetter;
        public Func<object, UInt32, object> uInt32Setter;
        public Func<object, UInt16, object> uint16Setter;
        public Func<object, Int64, object> uint64Setter;

        // Func<object, object[], object> genericSetter; This could be used to set anything but it would be slower.

        public SetterMethod(PropertyInfo info){
            this.int32Setter = GetValueSetter<Int32>(info);
            this.boolSetter = GetValueSetter<Boolean>(info);
            this.byteSetter = GetValueSetter<Byte>(info);
            this.sbyteSetter = GetValueSetter<sbyte>(info);
            this.charSetter = GetValueSetter<char>(info);
            this.decimalSetter = GetValueSetter<decimal>(info);
            this.doubleSetter = GetValueSetter<double>(info);
            this.singleSetter = GetValueSetter<Single>(info);
            this.uInt32Setter = GetValueSetter<UInt32>(info);
            this.int64Setter = GetValueSetter<Int64>(info);
            this.uint64Setter = GetValueSetter<Int64>(info);
            this.int16Setter = GetValueSetter<Int16>(info);
            this.uint16Setter = GetValueSetter<UInt16>(info);
            this.stringSetter = GetValueSetter<string>(info);
            this.dateTimeSetter = GetValueSetter<DateTime>(info);
            this.objectSetter = GetValueSetter<object>(info);
        }

        /// <summary>
        ///     Don't set value types here or you will box them.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="parentObject"></param>
        /// <param name="value"></param>
        public void SetValue(ReflectiveProperty reflectiveProperty, object parentObject, object value){
            if (reflectiveProperty.type != ReflectiveProperty.QuickType.UnDefinedObject){
                throw new ArgumentException();
            }
            //objectSetter(parentObject, value); //TODO: expression is not casting properly
            reflectiveProperty.info.SetValue(parentObject, value, null);
        }

        public void SetValue(ReflectiveProperty.QuickType type, object parentObject, string value){
            if (type == ReflectiveProperty.QuickType.Boolean){
                this.boolSetter(parentObject, Boolean.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.Int32){
                this.int32Setter(parentObject, Int32.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.Boolean){
                this.boolSetter(parentObject, Boolean.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.Byte){
                this.byteSetter(parentObject, Byte.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.SByte){
                this.sbyteSetter(parentObject, SByte.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.Char){
                this.charSetter(parentObject, Char.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.Decimal){
                this.decimalSetter(parentObject, Decimal.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.Double){
                this.doubleSetter(parentObject, Double.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.Single){
                this.singleSetter(parentObject, Single.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.UInt32){
                this.uInt32Setter(parentObject, UInt32.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.Int64){
                this.int64Setter(parentObject, Int64.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.Int64){
                this.int64Setter(parentObject, Int64.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.Int16){
                this.int16Setter(parentObject, Int16.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.UInt16){
                this.int16Setter(parentObject, Int16.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.String){
                this.stringSetter(parentObject, value);
            } else if (type == ReflectiveProperty.QuickType.DateTime){
                this.dateTimeSetter(parentObject, DateTime.Parse(value));
            } else if (type == ReflectiveProperty.QuickType.UnDefinedObject){
                this.objectSetter(parentObject, value);
            } else{
                throw new NotImplementedException();
            }
        }

        /*private Action<T, object> GetValueSetter<T>(PropertyInfo propertyInfo)
        {
            if (typeof(T) != propertyInfo.PropertyType)
            {
                return null;
            }


            /*var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
            var argument = Expression.Parameter(typeof(object), "a");
            var setterCall = Expression.Call( instance, propertyInfo.GetSetMethod(),
                                              Expression.Convert(argument, propertyInfo.PropertyType));
            return (Action<T, object>)Expression.Lambda(setterCall, instance, argument) .Compile();*/
        //}*
        /// <summary>
        ///     return a setter if the type is correct, otherwise null.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyInfo"></param>
        /// <returns></returns>
        /// <summary>
        ///     Generates direct access to the set method
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="info"></param>
        /// <returns></returns>
        private static Func<object, T, object> GetValueSetter<T>(PropertyInfo info){
            if (typeof(T) != info.PropertyType /*&& typeof(T) != typeof(object) */){
                return null;
            }

            MethodInfo methodInfo = info.GetSetMethod();
            var instanceParameter = Expression.Parameter(typeof(object), "instance");
            var parametersParameter = Expression.Parameter(typeof(T), "parameters");
            var parameterExpressions = new List<Expression>();
            var paramInfos = methodInfo.GetParameters();
            parameterExpressions.Add(parametersParameter);

            var instanceCast = methodInfo.IsStatic
                                   ? null
                                   : Expression.Convert(instanceParameter, methodInfo.ReflectedType);

            var methodCall = Expression.Call(instanceCast, methodInfo, parameterExpressions);
            var lambda = Expression.Lambda<Action<object, T>>(methodCall, instanceParameter, parametersParameter);

            Action<object, T> execute = lambda.Compile();
            return (instance, parameters) =>{
                execute(instance, parameters);
                return null;
            };
        }
    }
}